﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Tszy.Unlimited.Base.Collections;
using Tszy.Unlimited.Base.Extensions;

namespace Tszy.Unlimited.Base.Objects.Property
{
    /// <summary>
    ///
    /// </summary>
    public class PropertyManager
    {
        /// <summary>
        /// 当前实体管理器。
        /// </summary>
        public static PropertyManager Instance { get; } = new PropertyManager();

        private DateTime _expiredTime = new(2018, 12, 8);

        private MonoConcurrentDictionary<string, PropertyContainer> _objectContainers = new();

        /// <summary>
        ///
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <param name="obj"></param>
        /// <returns></returns>
        public IEnumerable<ObjectPropertyInfo> FindProperties<TSource>(TSource obj)
        {
            var key = obj.GetType().FullName;

            if (!_objectContainers.ContainsKey(key))
            {
                SaveProperties(key, obj.GetType());
            }

            return _objectContainers[key].Properties.Values;
        }

        /// <summary>
        ///
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <param name="obj"></param>
        /// <returns></returns>
        public PropertyContainer GetPropertyContainer<TSource>(TSource obj)
        {
            var key = obj.GetType().FullName;

            if (!_objectContainers.ContainsKey(key))
            {
                SaveProperties(key, obj.GetType());
            }

            return _objectContainers[key];
        }

        /// <summary>
        ///
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <param name="obj"></param>
        /// <param name="propertyName"></param>
        /// <param name="value"></param>
        public void SetProperty<TSource>(TSource obj, string propertyName, object value)
        {
            var key = obj.GetType().FullName;

            if (!_objectContainers.ContainsKey(key))
            {
                SaveProperties(key, obj.GetType());
            }

            var lowerPropertyName = propertyName.ToLower();

            var currentPropertyName = string.Empty;

            if (_objectContainers[key].Properties.Keys.Any())
            {
                foreach (var p in _objectContainers[key].Properties.Keys)
                {
                    if (p.ToLower() == lowerPropertyName)
                    {
                        currentPropertyName = p;
                        break;
                    }
                }
            }

            if (string.IsNullOrWhiteSpace(currentPropertyName))
            {
                return;
            }

            var property = _objectContainers[key].Properties[currentPropertyName];

            property.SetValue(obj, value);
        }

        private void SaveProperties(string key, Type sourceType)
        {
            Clear();

            try
            {
                var properties = new MonoConcurrentDictionary<string, ObjectPropertyInfo>();

                var objProperties = sourceType.GetProperties();

                if (objProperties.Any())
                {
                    foreach (var property in objProperties)
                    {
                        if (properties.ContainsKey(property.Name))
                        {
                            continue;
                        }

                        lock (this)
                        {
                            //MethodManager.Instance.Invoke(obj, property.Name);
                            var get = PropertyAccessor.CreateGetter(property);
                            var set = PropertyAccessor.CreateSetter(property);

                            var value = new ObjectPropertyInfo
                            {
                                Property = property,
                                Getter = get,
                                Setter = set,
                            };

                            properties[property.Name] = value;
                        }
                    }
                }

                _objectContainers[key] = new PropertyContainer
                {
                    Properties = properties,
                    ExpiredTime = DateTime.Now,
                };
            }
            catch (IndexOutOfRangeException)
            {
                //var logBuilder = new StringBuilder();

                //logBuilder.AppendLine($"属性管理器：更新对象 {key} 的属性失败");
                //logBuilder.AppendLine(indexOutOfRangeException.Message);
                //logBuilder.AppendLine(indexOutOfRangeException.StackTrace);

                //LoggerFactory.Current.Create().Fatal(logBuilder.ToString(), "PropertyManager", indexOutOfRangeException);

                lock (this)
                {
                    _objectContainers.Clear();
                    _objectContainers = new MonoConcurrentDictionary<string, PropertyContainer>();
                }
            }
        }

        private void Clear()
        {
            if (!_objectContainers.Any())
            {
                return;
            }

            var expiredTime = DateTime.Now.AddHours(-1);

            if (_expiredTime < expiredTime)
            {
                lock (this)
                {
                    _expiredTime = DateTime.Now;

                    if (_expiredTime < expiredTime)
                    {
                        foreach (var objectContainer in _objectContainers)
                        {
                            if (objectContainer.Value.ExpiredTime < expiredTime)
                            {
                                _objectContainers.Remove(objectContainer.Key);
                            }
                        }
                    }
                }
            }
        }
    }

    /// <summary>
    ///
    /// </summary>
    public class PropertyContainer
    {
        /// <summary>
        ///
        /// </summary>
        public MonoConcurrentDictionary<string, ObjectPropertyInfo> Properties = new();

        /// <summary>
        ///
        /// </summary>
        public DateTime ExpiredTime { get; set; } = DateTime.Now;
    }

    /// <summary>
    ///
    /// </summary>
    public class ObjectPropertyInfo
    {
        /// <summary>
        ///
        /// </summary>
        public PropertyInfo Property { get; set; }

        /// <summary>
        ///
        /// </summary>
        public string Name => Property.Name;

        /// <summary>
        ///
        /// </summary>
        public Type Type => Property.PropertyType;

        /// <summary>
        ///
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public object GetValue(object obj)
        {
            return Getter?.Invoke(obj);
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="value"></param>
        public void SetValue(object obj, object value)
        {
            Setter?.Invoke(obj, value);
        }

        /// <summary>
        ///
        /// </summary>
        internal Func<object, object> Getter { get; set; }

        /// <summary>
        ///
        /// </summary>
        internal Action<object, object> Setter { get; set; }
    }
}